<template>
  <video
    ref="video"
    :width="props.width"
    :height="props.height"
    :src="source"
    :autoplay="autoplay"
    :playsinline="playsinline"
  />
</template>

<script setup>
  import { onMounted, watch, onBeforeUnmount, getCurrentInstance } from 'vue';

  const { proxy: ctx } = getCurrentInstance();
  const props = defineProps({
    width: {
      type: [Number, String],
      default: "100%"
    },
    height: {
      type: [Number, String],
      default: 500
    },
    autoplay: {
      type: Boolean,
      default: true
    },
    screenshotFormat: {
      type: String,
      default: "image/jpeg"
    },
    selectFirstDevice: {
      type: Boolean,
      default: false
    },
    deviceId: {
      type: String,
      default: null
    },
    playsinline: {
      type: Boolean,
      default: true
    },
    resolution: {
      type: Object,
      default: null,
      validator: value => {
        return value.height && value.width;
      }
    }
  });

  let video = $ref(null);
  let source = $ref(null);
  // let canvas = $ref(null);
  let camerasListEmitted = false;
  let cameras = [];

  /**
   * get user media
   */
  function legacyGetUserMediaSupport() {
    return constraints => {
      // First get ahold of the legacy getUserMedia, if present
      let getUserMedia =
        navigator.getUserMedia ||
        navigator.webkitGetUserMedia ||
        navigator.mozGetUserMedia ||
        navigator.msGetUserMedia ||
        navigator.oGetUserMedia;
      // Some browsers just don't implement it - return a rejected promise with an error
      // to keep a consistent interface
      if (!getUserMedia) {
        return Promise.reject(
          new Error("getUserMedia is not implemented in this browser")
        );
      }
      // Otherwise, wrap the call to the old navigator.getUserMedia with a Promise
      return new Promise(function (resolve, reject) {
        getUserMedia.call(navigator, constraints, resolve, reject);
      });
    };
  }

  /**
   * setup media
   */
  function setupMedia() {
    if (navigator.mediaDevices === undefined) {
      navigator.mediaDevices = {};
    }
    if (navigator.mediaDevices.getUserMedia === undefined) {
      navigator.mediaDevices.getUserMedia = legacyGetUserMediaSupport();
    }
    testMediaAccess();
  }

  /**
   * load available cameras
   */
  function loadCameras() {
    navigator.mediaDevices
      .enumerateDevices()
      .then(deviceInfos => {
        for (let i = 0; i !== deviceInfos.length; ++i) {
          let deviceInfo = deviceInfos[i];
          if (deviceInfo.kind === "videoinput") {
            cameras.push(deviceInfo);
          }
        }
      })
      .then(() => {
        if (!camerasListEmitted) {
          if (props.selectFirstDevice && cameras.length > 0) {
            props.deviceId = cameras[0].deviceId;
          }
          ctx.$emit("cameras", cameras);
          camerasListEmitted = true;
        }
      })
      .catch(error => ctx.$emit("notsupported", error));
  }

  /**
   * change to a different camera stream, like front and back camera on phones
   */
  function changeCamera(deviceId) {
    stop();
    ctx.$emit("camera-change", deviceId);
    loadCamera(deviceId);
  }

  /**
   * load the stream to the
   */
  function loadSrcStream(stream) {
    if ("srcObject" in ctx.$refs.video) {
      // new browsers api
      ctx.$refs.video.srcObject = stream;
    } else {
      // old broswers
      source = window.HTMLMediaElement.srcObject(stream);
    }
    // Emit video start/live event
    ctx.$refs.video.onloadedmetadata = () => {
      ctx.$emit("video-live", stream);
    };
    ctx.$emit("started", stream);
  }

  /**
   * stop the selected streamed video to change camera
   */
  function stopStreamedVideo(videoElem) {
    let stream = videoElem.srcObject;
    let tracks = stream.getTracks();
    tracks.forEach(track => {
      // stops the video track
      track.stop();
      ctx.$emit("stopped", stream);
      ctx.$refs.video.srcObject = null;
      source = null;
    });
  }

  // stop the video
  function stop() {
    if (ctx.$refs.video !== null && ctx.$refs.video.srcObject) {
      stopStreamedVideo(ctx.$refs.video);
    }
  }

  // start the video
  function start() {
    if (props.deviceId) {
      loadCamera(props.deviceId);
    }
  }

  // pause the video
  function pause() {
    if (ctx.$refs.video !== null && ctx.$refs.video.srcObject) {
      ctx.$refs.video.pause();
    }
  }

  // resume the video
  function resume() {
    if (ctx.$refs.video !== null && ctx.$refs.video.srcObject) {
      ctx.$refs.video.play();
    }
  }

  /**
   * test access
   */
  function testMediaAccess() {
    let constraints = { video: true };
    if (props.resolution) {
      constraints.video = {};
      constraints.video.height = props.resolution.height;
      constraints.video.width = props.resolution.width;
    }
    navigator.mediaDevices
      .getUserMedia(constraints)
      .then(stream => {
        //Make sure to stop this MediaStream
        let tracks = stream.getTracks();
        tracks.forEach(track => {
          track.stop();
        });
        loadCameras();
      })
      .catch(error => ctx.$emit("error", error));
  }

  /**
   * load the camera passed as index!
   */
  function loadCamera(device) {
    let constraints = { video: { deviceId: { exact: device } } };
    if (props.resolution) {
      constraints.video.height = props.resolution.height;
      constraints.video.width = props.resolution.width;
    }
    navigator.mediaDevices
      .getUserMedia(constraints)
      .then(stream => loadSrcStream(stream))
      .catch(error => ctx.$emit("error", error));
  }

  /**
   * capture screenshot
   */
  function capture() {
    return getCanvas().toDataURL(props.screenshotFormat);
  }

  /**
   * get canvas
   */
  function getCanvas() {
    // let video = ctx.$refs.video;
    let canvas = document.createElement("canvas");
    canvas.height = video.videoHeight;
    canvas.width = video.videoWidth;
    let canvas_ctx = canvas.getContext("2d");
    canvas_ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
    return canvas;
  }

  watch(() => props.deviceId, deviceId => {
    changeCamera(deviceId);
  })

  onMounted(() => {
    setupMedia();
  })

  onBeforeUnmount(() => {
    stop();
  })

  defineExpose({
    start,
    stop,
    pause,
    resume,
    loadCameras,
    loadCamera,
    changeCamera,
    capture,
    getCanvas
  });
</script>

<style lang="scss" scoped>
</style>